# This file is part of Gehyra.
#
# Gehyra is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Gehyra is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gehyra.  If not, see <http://www.gnu.org/licenses/>.

"""
@package gehyra.core.gehyra_node
Top level code for a regular gehyra nodes.
$Id: gehyra_node.py 450 2011-01-20 14:28:47Z andyhhp@gmail.com $
"""

"""
@file gehyra/core/gehyra_node.py
Top level code for a regular gehyra nodes
"""

from gehyra.common.logger import LOG
from gehyra.common.interfaces import (IGlobalObserver, IManager)
from gehyra.bootstrap.manager import Manager as BootstrapManager
from gehyra.entity.manager import Manager as EntityManager
from gehyra.network.manager import Manager as NetworkManager

# from twisted.internet.threads import deferToThread
from twisted.internet import reactor
from zope.interface import implements
from zope.interface.verify import verifyObject
from zope.interface.exceptions import (BrokenMethodImplementation,
                                      BrokenImplementation)

class GehyraNode(object):
    """Top level singleton class for regualr gehyra nodes.
    Responsible for initialising other components and keeping things running.
    """
    implements(IManager)

    ## State ID for initialize
    STATE_INITIALIZE = 1
    ## State ID for waiting offline
    STATE_OFFLINE = 2
    ## State ID for waiting to sync
    STATE_SYNCING = 3
    ## State ID for waiting online
    STATE_ONLINE = 4

    ## Event ID for once initialization is complete
    EVENT_STARTUP = 1
    ## Event ID for starting the network synchronization process
    EVENT_STARTSYNC = 2
    ## Event ID for bootstrap configuration data being successfully updated
    EVENT_BOOTSTRAP_UPDATE = 3

    def __init__(self, cfg):
        """Constructor.
        """

        ## Main gehyra object (mandated by IManager) which is just self
        self.main = self
        cfg.main = self

        ## Configuration Manager object
        self.config_manager = cfg

        ## Bootstrap Manager object
        self.bootstrap_manager = BootstrapManager(self)
        ## Entity Manager object
        self.entity_manager = EntityManager(self)
        ## Network Manager object
        self.network_manager = NetworkManager(self)

        ## Current state
        self.state = self.STATE_INITIALIZE
        ## Last event
        self.last_event = None
        ## List of observers
        self.observers = []

    def initialize(self):
        """Initalise the components.
        Responsible for doing setup for the components, before normal operation
        begins.
        """

        init_list = [ self.config_manager,
                      self.bootstrap_manager,
                      self.entity_manager,
                      self.network_manager,
                      ]

        for i in init_list:
            if i.initialize() is not True:
                return False

        return True


    def start(self):
        """Start the node"""
        reactor.callWhenRunning(self._notify_event, self.EVENT_STARTUP)
        reactor.run()
          

    def hook_observer(self, obj):
        """Add an observer.
        Checks to see whether the object is already hooked, and whether that
        object implements the IStateObserver interface.  If passed, the object 
        will now recieve notifications.
        @param obj Object to register as a listener
        """
        
        if obj in self.observers:
            LOG.warn("Attempting to hook already hooked observer '%s'" % obj)
            return


        if IGlobalObserver.implementedBy(obj.__class__) is not True:
            LOG.warn("Attempting to hook a non IGlobalObserver object '%s'"
                     % obj)
            return

        try:
            verifyObject(IGlobalObserver, obj)
        except (BrokenMethodImplementation, BrokenImplementation) as e:
            LOG.error("Attempting to hook invalid object %s - %s" % (obj, e))
            return
        
        self.observers.append(obj)

    def unhook_observer(self, obj):
        """Remove an observer.
        The specified object will no longer receive any notifications.
        @param obj Object to unhook as an observer
        """
        try:
            self.observers.remove(obj)
        except ValueError:
            LOG.warn("Attempting to unhook %s failed as object not currently"
                     "hooked" % obj)

    def _notify_event(self, event):
        """Notify observers that an event has occured.
        Loops through all current observers and calls their on_event functions
        @param event Event ID to notify about
        """
        ename = [i for i in dir(GehyraNode) if i[:6] == "EVENT_" and
                 getattr(GehyraNode,i) == event][0]
        LOG.debug("Event %s" % ename)
        for l in self.observers:
            l.on_event(event)
        self.last_event = event

    def _notify_state(self, state):
        """Notify observers that an state change has occured.
        Loops through all current observers and calls their on_state functions
        @param state State ID to notify about
        """
        sname = [i for i in dir(GehyraNode) if i[:6] == "STATE_" and
                 getattr(GehyraNode,i) == state][0]
        LOG.debug("State Change %s" % sname)
        for l in self.observers:
            l.on_state(state)
        self.state = state


    def request_sync(self):
        """Sub objects requesting a E_STARTSYNC change state to S_SYNCING.
        Using reactor.callLater so we dont execute depth-first through return
        calls in the _notify functions.
        """
        reactor.callLater(0, self._notify_event, self.EVENT_STARTSYNC)
        reactor.callLater(0, self._notify_state, self.STATE_SYNCING)

    def bootstrap_updated(self):
        """Bootstrap posting a 'Dynamic Configuration Data updated' event"""
        reactor.callLater(0, self._notify_event, self.EVENT_BOOTSTRAP_UPDATE)
        LOG.debug("Boostrap Config: %s" % self.bootstrap_manager.latest)
